////////////////////////////////////////////////////////////////////
// File:	Interpolator.cpp
// Purpose: Interpolates a specified "instanced" animation from it's 
//			bind pose to it's current animation specified. Used as a 
//			media player for the animation (play, stop, pause.)
// Author:	Jay Baird
////////////////////////////////////////////////////////////////////

#include "Interpolator.h"
#include "AnimationManager.h"
#include "MeshManager.h"
#include <glee.h>

using namespace MeshComponent;
using namespace AnimationComponent;

Interpolator::Interpolator() : m_currFrameIndex(0), m_pAnimation(0), m_pBindPose(0), 
							   m_animationID(-1), m_bindPoseID(-1), m_bIsPaused(false),
							   m_currentTime(0.0f), m_bindFrameIndex(0), m_loopCount(0)
{

}

Interpolator::~Interpolator()
{

}

void Interpolator::MapWeightedVertices(int meshID, unsigned numInfluences) const
{
	glBindBuffer(GL_ARRAY_BUFFER, MeshManager::GetInstance()->GetMeshBufferID(m_meshID, MESH_BUFFER_TYPE_VERTEX));
	glBufferData(GL_ARRAY_BUFFER, sizeof(vec3) * numInfluences, NULL, GL_DYNAMIC_DRAW);
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vec3) * numInfluences, &m_weightedVerts[0]);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}

bool Interpolator::SetAnimation(int id)
{
	if(!AnimationManager::m_instance.IsAvailable(id)
	|| !MeshManager::GetInstance()->IsAvailable(m_meshID))
		return false;

	// Animation is already set to this animation
	if(m_animationID == id) return true;

	// Unpause
	m_bIsPaused = false;

	m_loopCount = 0;
	m_currentTime = 0.0f;
	m_currFrameIndex = 0;
	m_animationID = id;
	m_pAnimation = &AnimationManager::m_instance.m_animations[id].anim;

	// Resize the weighted Verts list
	unsigned numVerts = MeshManager::GetInstance()->GetVertexCount(m_meshID);

	m_weightedVerts.clear();
	m_weightedVerts.resize(numVerts);

	const vec3* pVerts = MeshManager::GetInstance()->GetVertices(m_meshID);
	memcpy(&m_weightedVerts[0], pVerts, numVerts * sizeof(vec3));

	MapWeightedVertices(m_meshID, numVerts);

 	// Set the current keyframe to the animations first frame
	m_currKeyFrame = m_pAnimation->m_keyFrames[0];

	return true;
}

bool Interpolator::SetBindPose(int id, unsigned bindFrameIndex, int meshID)
{
	if(!AnimationManager::m_instance.IsAvailable(id)
	|| !MeshManager::GetInstance()->IsAvailable(meshID))
		return false;

	m_pBindPose = &AnimationManager::m_instance.m_animations[id].anim;
	
	if(bindFrameIndex >= (unsigned)m_pBindPose->m_keyFrames.size())
	{
		m_pBindPose = NULL;
		return false;
	}

	// Bind pose is already set to this id
	if(m_bindPoseID == id) return true;

	m_meshID = meshID;
	m_loopCount = 0;
	m_currentTime = 0.0f;
	m_currFrameIndex = 0;
	m_bindFrameIndex = bindFrameIndex;
	m_bindPoseID = id;

	// Generate the LUT for the inverse bind poses
	unsigned boneCount = (unsigned)m_pBindPose->m_keyFrames[bindFrameIndex].transforms.size();
	unsigned vertexCount = MeshManager::GetInstance()->GetVertexCount(meshID);
	const vec3* pVerts = MeshManager::GetInstance()->GetVertices(meshID);

	// Generate inv bind mats for every bone in the bind pose animation
	std::deque<matrix4> invMats;
	invMats.resize(boneCount);
	for (unsigned i = 0; i < boneCount; i++)
		invMats[i] = Inverse(m_pBindPose->m_keyFrames[m_bindFrameIndex].transforms[i].world);

	// Set up the LUT
	m_bindTable.resize(vertexCount);
	for (unsigned i = 0; i < vertexCount; i++)
	{
		m_bindTable[i].resize(boneCount);

		for (unsigned j = 0; j < boneCount; j++)
		{
			m_bindTable[i][j] = invMats[j] * pVerts[i];
		}
	}

	return true;
}

bool Interpolator::Process(float fTimeStep)
{
	if(m_bIsPaused
	|| !fTimeStep
	|| !AnimationManager::m_instance.IsAvailable(m_animationID)
	|| !AnimationManager::m_instance.IsAvailable(m_bindPoseID)
	|| !MeshManager::GetInstance()->IsAvailable(m_meshID))
	{
		return false;
	}

	// Our animation is just a bind pose so don't process or apply anything
	if(m_pAnimation == m_pBindPose)
	{
		m_currKeyFrame = m_pBindPose->m_keyFrames[m_bindFrameIndex];
		return false;
	}

	unsigned numKeyFrames = (unsigned)m_pAnimation->m_keyFrames.size();

	// Apply Smooth Skinning...
	float dt = fTimeStep;

	// Increase currentTime by timestep
	m_currentTime += dt;

	// If current time is less than zero then go up by duration
	while(m_currentTime < 0.0f)
		m_currentTime += m_pAnimation->m_duration;

	// if currentTime is greater than duration then down by duration
	// We will also reset to the first frame index
	while(m_currentTime > m_pAnimation->m_duration)
	{
		m_currentTime -= m_pAnimation->m_duration;
		m_loopCount++;
	}

	// If the current Time is greater than current frames keyTime (how long it takes till the next keyFrame)
	// Then go to the next frame
	for(unsigned i = 0; i < numKeyFrames; i++)
	{
		if(m_currentTime >= m_pAnimation->m_keyFrames[i].keyTime)
			m_currFrameIndex = i;
	}

	// Reset pointers to frames
	const KeyFrame& pCurrKeyFrame = m_pAnimation->m_keyFrames[m_currFrameIndex];
	const KeyFrame& pNextKeyFrame = (m_currFrameIndex != numKeyFrames - 1) ? m_pAnimation->m_keyFrames[m_currFrameIndex + 1]
																		   : m_pAnimation->m_keyFrames[0];

	// Figure out the lamda (currTime - currFrame.time) / currFrame.tweenTime
	float tweenTime = (m_currFrameIndex != numKeyFrames - 1) ? pNextKeyFrame.keyTime - pCurrKeyFrame.keyTime
															 : m_pAnimation->m_duration - pCurrKeyFrame.keyTime;

	float T = (m_currentTime - pCurrKeyFrame.keyTime) / tweenTime;

	// Interpolate frame-to-frame
	unsigned numTransforms = (unsigned)m_currKeyFrame.transforms.size();
	for (unsigned i = 0; i < numTransforms; i++)
	{
		m_currKeyFrame.transforms[i].local = Interpolate(pCurrKeyFrame.transforms[i].local, 
														 pNextKeyFrame.transforms[i].local, T);
	}
	
	//unsigned meshID = m_pAnimation->GetMeshID();
	const Bone* pBones = MeshManager::GetInstance()->GetBones(m_meshID);
	const std::vector<std::vector<Influence>>& influences = (*MeshManager::GetInstance()->GetInfluences(m_meshID));
	const vec3* pVerts = MeshManager::GetInstance()->GetVertices(m_meshID);

	// Flatten the matrices...
	TraverseKeyFrame(0, pBones, m_currKeyFrame);

	Matrix4 invBind;

	// Apply weights to vertices...
	unsigned numInfluences = (unsigned)influences.size();
	for(unsigned i = 0; i < numInfluences; i++)
	{
		m_weightedVerts[i] = vec3();	// Reset to zero

		unsigned numWeights = (unsigned)influences[i].size();
		for (unsigned j = 0; j < numWeights; j++)
		{
			const int& boneIndex = influences[i][j].boneIndex;

			// wi*Ci(Bi-1v)
			m_weightedVerts[i] += ((m_currKeyFrame.transforms[boneIndex].world * 
								    m_bindTable[i][boneIndex]) * influences[i][j].weight);

		}
	}

	MapWeightedVertices(m_meshID, numInfluences);

	return true;
}